package com.xqboss.apps.service.user;

import cn.hutool.core.lang.Pair;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xqboss.apps.constant.LockKey;
import com.xqboss.apps.domain.box.Box;
import com.xqboss.apps.domain.box.BoxNft;
import com.xqboss.apps.domain.nft.Category;
import com.xqboss.apps.domain.nft.NftNumber;
import com.xqboss.apps.domain.order.OrderItem;
import com.xqboss.apps.domain.user.SysUserWallet;
import com.xqboss.apps.domain.user.UserBox;
import com.xqboss.apps.domain.user.UserBoxNft;
import com.xqboss.apps.dto.user.OpenBoxDto;
import com.xqboss.apps.dto.user.OpenBoxInfoDto;
import com.xqboss.apps.enums.market.MarketTypeEnum;
import com.xqboss.apps.enums.market.UserBoxNftStatusEnum;
import com.xqboss.apps.enums.user.UserWalletEnum;
import com.xqboss.apps.enums.user.WalletChangeTypeEnum;
import com.xqboss.apps.mapper.user.UserBoxMapper;
import com.xqboss.apps.service.box.BoxService;
import com.xqboss.apps.service.market.MarketService;
import com.xqboss.apps.service.nft.CategoryService;
import com.xqboss.apps.util.mh.BeanCopierUtil;
import com.xqboss.apps.vo.box.AppUserBoxVo;
import com.xqboss.apps.vo.box.OpenBoxInfoVo;
import com.xqboss.apps.vo.box.UserBoxInfoVo;
import com.xqboss.apps.vo.user.UserNFTItemVo;
import com.xqboss.common.core.domain.R;
import com.xqboss.common.core.domain.entity.SysUser;
import com.xqboss.common.core.redis.lock.LockValue;
import com.xqboss.common.core.redis.lock.RedisLock;
import com.xqboss.common.exception.ServiceException;
import com.xqboss.system.service.ISysConfigService;
import com.xqboss.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 用户箱子服务
 */
@Slf4j
@Service
public class UserBoxService extends ServiceImpl<UserBoxMapper, UserBox> {

    @Autowired
    private ISysUserService sysUserService;
    @Autowired
    private SysUserWalletService userWalletService;
    @Autowired
    private CategoryService categoryService;
    @Autowired
    private UserBoxNftService userBoxNftService;
    @Lazy
    @Autowired
    private BoxService boxService;

    @Lazy
    @Autowired
    private MarketService marketService;


    /**
     * 获取用户的箱子列表
     *
     * @param userId
     * @return
     */
    public R<List<AppUserBoxVo>> getUserBoxList(Long userId) {
        List<UserBox> list = list(new LambdaQueryWrapper<UserBox>()
                .select(UserBox::getId,
                        UserBox::getBoxId,
                        UserBox::getUserId,
                        UserBox::getQuantity,
                        UserBox::getName,
                        UserBox::getHeadimg,
                        UserBox::getCategoryName,
                        UserBox::getPcName,
                        UserBox::getPublishName,
                        UserBox::getIsLock)
                .eq(UserBox::getUserId, userId)
                .gt(UserBox::getQuantity, 0)
                .orderByDesc(UserBox::getId));
        Map<Long, Boolean> boxResellMap = new HashMap<>();
        List<AppUserBoxVo> result = list.stream().map(userBox -> {
            AppUserBoxVo vo = BeanCopierUtil.copy(userBox, AppUserBoxVo.class);
            Boolean resell = boxResellMap.get(vo.getBoxId());
            if (resell == null) {
                resell = boxService.isBoxResell(userBox.getBoxId());
                boxResellMap.put(userBox.getBoxId(), resell);
            }
            vo.setIsResell(resell);
            return vo;
        }).collect(Collectors.toList());
        return R.ok(result, list);
    }

    /**
     * 用户开箱
     *
     * @param request
     * @return
     */
    @RedisLock(lockKey = LockKey.LOCK_USER_BOX)
    @Transactional(rollbackFor = Exception.class)
    public List<UserNFTItemVo> openBox(@LockValue Long userId, OpenBoxDto request) {
        UserBox userBox = getOne(new LambdaQueryWrapper<UserBox>()
                .eq(UserBox::getUserId, userId)
                .eq(UserBox::getId, request.getUserBoxId()));
        if (userBox == null) {
            throw new ServiceException("宝箱不存在！");
        }
        if (request.getQuantity() > userBox.getQuantity()) {
            throw new ServiceException("箱子数量不足！");
        } else if (userBox.getIsLock() == 1) {
            throw new ServiceException("宝盒异常，暂时无法打开！");
        }
        SysUser user = sysUserService.selectUserById(userId);
        // 计算消耗积分
        BigDecimal useInteger;
        if (request.getQuantity() <= user.getFreeOpenBox()) {
            useInteger = BigDecimal.ZERO;
        } else {
            int payCount = request.getQuantity() - user.getFreeOpenBox();
            Box box = boxService.getBoxById(userBox.getBoxId());
            BigDecimal openUseInteger = BooleanUtils.isTrue(box.getAutoOpen()) ? BigDecimal.ZERO : box.getOpenHotBeans();
            useInteger = openUseInteger.multiply(BigDecimal.valueOf(payCount)).setScale(2, RoundingMode.HALF_UP);
        }

        // 扣除箱子免费额度
        int useFreeOpenBox = Math.min(user.getFreeOpenBox(), request.getQuantity());
        if (useFreeOpenBox > 0) {
            boolean result = sysUserService.useFreeOpenBox(userId, useFreeOpenBox);
            if (!result) {
                throw new ServiceException("开箱失败！");
            }
        }

        if (useInteger.compareTo(request.getUseInteger()) != 0) {
            throw new ServiceException("请重新计算消耗热豆值");
        }

        // 扣除积分
        if (useInteger.compareTo(BigDecimal.ZERO) > 0) {
            userWalletService.reduce(user.getUserId(), UserWalletEnum.INTEGRAL, useInteger, WalletChangeTypeEnum.OPEN_BOX, userBox.getId(), WalletChangeTypeEnum.OPEN_BOX.getMsg());
        }

        // 扣除箱子数量
        boolean update = update(new LambdaUpdateWrapper<UserBox>()
                .setSql("quantity=quantity-" + request.getQuantity())
                .eq(UserBox::getId, userBox.getId())
                .ge(UserBox::getQuantity, request.getQuantity()));
        if (!update) {
            throw new ServiceException("操作失败！");
        }

        // 分发藏品信息
        List<UserNFTItemVo> result = userBoxNftService.openBox(userBox, request.getQuantity());
        return result;
    }

    /**
     * 获取开箱信息
     *
     * @param request
     * @return
     */
    public OpenBoxInfoVo getOpenBoxInfo(Long userId, OpenBoxInfoDto request) {
        OpenBoxInfoVo vo = new OpenBoxInfoVo();
        UserBox userBox = getOne(new LambdaQueryWrapper<UserBox>()
                .eq(UserBox::getUserId, userId)
                .eq(UserBox::getId, request.getUserBoxId()));
        if (userBox == null) {
            throw new ServiceException("宝箱不存在！");
        }
        SysUser user = sysUserService.selectUserById(userId);
        SysUserWallet wallet = userWalletService.getWallet(userId, UserWalletEnum.INTEGRAL);

        vo.setBoxId(userBox.getBoxId());
        vo.setUserBoxId(userBox.getId());
        vo.setQuantity(userBox.getQuantity());
        vo.setFreeOpenBoxCount(user.getFreeOpenBox());
        vo.setMaxOpenCount(10);
        vo.setInteger(wallet.getAmount());
        if (request.getQuantity() <= user.getFreeOpenBox()) {
            vo.setUseInteger(BigDecimal.ZERO);
        } else {
            int payCount = request.getQuantity() - user.getFreeOpenBox();
            Box box = boxService.getBoxById(userBox.getBoxId());
            BigDecimal openUseInteger = BooleanUtils.isTrue(box.getAutoOpen()) ? BigDecimal.ZERO : box.getOpenHotBeans();
            BigDecimal useInteger = openUseInteger.multiply(BigDecimal.valueOf(payCount)).setScale(2, RoundingMode.HALF_UP);
            vo.setUseInteger(useInteger);
        }
        return vo;
    }

    /**
     * 添加用户箱子
     *
     * @param userId
     * @param box
     * @param orderMainId
     * @param nftList
     * @param quantity
     * @param saleableTime 可出售时间
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @RedisLock(lockKey = LockKey.LOCK_USER_BOX)
    public UserBox addUserBox(@LockValue Long userId, Long orderMainId, Long orderItemId, Box box, List<Pair<BoxNft, List<NftNumber>>> nftList, Integer quantity, LocalDateTime saleableTime) {
        // 先获取用户当前的箱子，如果有就增加数量，否则新建
        UserBox userBox = getOne(new LambdaQueryWrapper<UserBox>()
                .eq(UserBox::getUserId, userId)
                .eq(UserBox::getBoxId, box.getId()));
        boolean save;
        if (userBox == null) {
            userBox = new UserBox();
            userBox.setUserId(userId);
            userBox.setBoxId(box.getId());
            userBox.setName(box.getName());
            userBox.setHeadimg(box.getHeadimg());
            userBox.setDescription(box.getDescription());
            userBox.setQuantity(quantity);
            userBox.setCategoryId(box.getCategoryId());
            if (box.getCategoryId() != null) {
                Category category = categoryService.selectCategoryById(Math.toIntExact(box.getCategoryId()));
                if (category != null) {
                    userBox.setCategoryName(category.getName());
                    if (category.getParentId() != null) {
                        Category pgCategory = categoryService.selectCategoryById(category.getParentId());
                        if (pgCategory != null) {
                            userBox.setPcName(pgCategory.getName());
                        }
                    }
                }
            }
            userBox.setAuthorId(box.getAuthorId());
            userBox.setAuthorName(box.getAuthorName());
            userBox.setAuthorHeadimg(box.getAuthorHeadimg());
            userBox.setPublishId(box.getPublishId());
            userBox.setPublishName(box.getPublishName());
            userBox.setPublishHeadimg(box.getPublishHeadimg());
            save = save(userBox);
        } else {
            save = update(new LambdaUpdateWrapper<UserBox>()
                    .eq(UserBox::getId, userBox.getId())
                    .setSql("quantity=quantity+" + quantity));
        }
        if (!save) {
            throw new ServiceException("保存失败！");
        }
        userBoxNftService.saveUserBoxNftList(orderMainId, orderItemId, userBox, nftList, saleableTime);
        return userBox;
    }

    /**
     * 获取宝箱详情
     *
     * @param userBoxId
     * @return
     */
    public UserBoxInfoVo getBoxDetail(Long userBoxId) {
        UserBox userBox = getById(userBoxId);
        if (userBox == null) {
            throw new ServiceException("宝箱不存在！");
        }

        UserBoxInfoVo vo = new UserBoxInfoVo();
        vo.setId(userBox.getId());
        vo.setBoxId(userBox.getBoxId());
        vo.setName(userBox.getName());
        vo.setHeadimg(userBox.getHeadimg());
        vo.setDescription(userBox.getDescription());
        vo.setCategoryName(userBox.getCategoryName());
        vo.setPcName(userBox.getPcName());
        vo.setAuthorId(userBox.getAuthorId());
        vo.setAuthorName(userBox.getAuthorName());
        vo.setAuthorHeadimg(userBox.getAuthorHeadimg());
        vo.setPublishName(userBox.getPublishName());
        return vo;
    }

    /**
     * 回退库存
     * @param sourceId
     * @param quantity
     */
    @RedisLock(lockKey = LockKey.LOCK_USER_BOX)
    public void rollbackStock(@LockValue Long userId, Long userBoxId, Integer quantity) {
        UserBox userBox = getById(userBoxId);
        if(userBox == null){
            log.error("用户宝箱回退库存失败，用户宝箱ID不存在：{}", userBoxId);
            throw new ServiceException("操作失败！");
        }
        userBoxNftService.cancelSell(userBox.getId(), quantity);

        boolean update = update(new LambdaUpdateWrapper<UserBox>()
                .setSql("quantity = quantity + " + quantity)
                .eq(UserBox::getId, userBoxId));
        if(!update){
            throw new ServiceException("操作失败！");
        }
    }

    /**
     * 转移宝箱给用户
     * @param orderItem
     * @param sourceId
     * @param sellerUserId
     * @param buyerUserId
     * @param count
     */
    @RedisLock(lockKey = LockKey.LOCK_USER_BOX)
    @Transactional(rollbackFor = Exception.class)
    public void moveUserBoxToUser(OrderItem orderItem, Long sourceId, @LockValue Long sellerUserId, Long buyerUserId, Integer count) {
        //TODO yutong 转移用户宝箱给买家，这里的锁应该锁卖家和买家的箱子，防止出错

        UserBox sellerUserBox = getById(sourceId);

        UserBox userBox = getOne(new LambdaQueryWrapper<UserBox>()
                .eq(UserBox::getUserId, buyerUserId)
                .eq(UserBox::getBoxId, sellerUserBox.getBoxId()));
        boolean save;
        if(userBox == null){
            userBox = BeanCopierUtil.copy(sellerUserBox, UserBox.class);
            userBox.setId(null);
            userBox.setUserId(buyerUserId);
            userBox.setQuantity(count);
            save = save(userBox);
        }else{
            save = update(new LambdaUpdateWrapper<UserBox>()
                    .eq(UserBox::getId, userBox.getId())
                    .setSql("quantity=quantity+" + count));
        }
        if (!save) {
            log.error("转移宝箱失败，请处理异常, 宝箱ID：{}, 买家：{}", sourceId, buyerUserId);
            throw new ServiceException("交易失败，请联系客服！");
        }

        // 获取用户当前未开箱的列表， 并将未开箱指定箱子数量发给买家
        userBoxNftService.moveBoxToUser(orderItem, sellerUserBox, userBox, count);
    }

    @RedisLock(lockKey = LockKey.LOCK_USER_BOX)
    @Transactional(rollbackFor = Exception.class)
    public void moveUserBoxToUser(Long sourceId, @LockValue Long fromUserId, Long toUserId, Integer count) {
        UserBox sellerUserBox = getById(sourceId);

        UserBox userBox = getOne(new LambdaQueryWrapper<UserBox>()
                .eq(UserBox::getUserId, toUserId)
                .eq(UserBox::getBoxId, sellerUserBox.getBoxId()));
        boolean save;
        if(userBox == null){
            userBox = BeanCopierUtil.copy(sellerUserBox, UserBox.class);
            userBox.setId(null);
            userBox.setUserId(toUserId);
            userBox.setQuantity(count);
            save = save(userBox);
        }else{
            save = update(new LambdaUpdateWrapper<UserBox>()
                    .eq(UserBox::getId, userBox.getId())
                    .setSql("quantity=quantity+" + count));
        }
        if (!save) {
            log.error("转移宝箱失败，请处理异常, 宝箱ID：{}, 流入用户：{}", sourceId, toUserId);
            throw new ServiceException("交易失败，请联系客服！");
        }
        // 来源用户扣数量
        save = update(new LambdaUpdateWrapper<UserBox>()
                .eq(UserBox::getId, sellerUserBox.getId())
                .setSql("quantity=quantity-" + count));
        if (!save) {
            log.error("转移宝箱失败，请处理异常, 宝箱ID：{}, 来源用户：{}", sourceId, fromUserId);
            throw new ServiceException("交易失败，请联系客服！");
        }

        // 获取用户当前未开箱的列表， 并将未开箱指定箱子数量发给买家
        userBoxNftService.moveBoxToUser(sellerUserBox, userBox, count);
    }


    /**
     * 获取用户箱子
     *
     * @param userBoxId
     * @param userId
     * @return
     */
    public UserBox getUserBox(Long userBoxId, Long userId) {
        UserBox userBox = getById(userBoxId);
        if(userBox == null || !Objects.equals(userBox.getUserId(), userId)){
            throw new ServiceException("宝箱不存在！");
        }
        return userBox;
    }

    /**
     * 获取用户箱子
     *
     * @param userBoxId
     * @param userId
     * @return
     */
    public UserBox getUserBoxByBoxId(Long userId, Long boxId) {
        UserBox userBox = getOne(new LambdaQueryWrapper<UserBox>()
                .eq(UserBox::getUserId, userId)
                .eq(UserBox::getBoxId, boxId));
        if(userBox == null){
            Box box = boxService.getBoxById(boxId);
            userBox = BeanCopierUtil.copy(box, UserBox.class);
            userBox.setId(null);
            userBox.setQuantity(0);
        }
        return userBox;
    }

    /**
     * 扣除库存
     * @param userBoxId
     * @param quantity
     */
    @RedisLock(lockKey = LockKey.LOCK_USER_BOX)
    @Transactional(rollbackFor = Exception.class)
    public void takeStock(Long userBoxId, Integer quantity) {
        boolean update = update(new LambdaUpdateWrapper<UserBox>()
                .setSql("quantity = quantity - " + quantity)
                .ge(UserBox::getQuantity, quantity)
                .eq(UserBox::getId, userBoxId));

        if(!update){
            throw new ServiceException("库存不足！");
        }
        userBoxNftService.addSell(userBoxId, quantity);
    }

    /**
     * @param userId
     * @param boxId
     * @return
     */
    public int getHoldBoxCount(Long userId, Long boxId) {
        UserBox userBox = getOne(Wrappers.lambdaQuery(UserBox.class)
                .select()
                .eq(UserBox::getUserId, userId)
                .eq(UserBox::getBoxId, boxId));
        return userBox == null ? 0 : userBox.getQuantity();
    }

    /**
     * 合成销毁
     * @param userId
     * @param boxId
     * @param num
     */
    public void synthesisBurn(Long userId, Long boxId, Integer num) {
        UserBox userBox = getOne(new LambdaQueryWrapper<UserBox>()
                .eq(UserBox::getUserId, userId)
                .eq(UserBox::getBoxId, boxId));
        if (userBox == null) {
            throw new ServiceException("用户未持有该宝箱！");
        }
        if (num > userBox.getQuantity()) {
            throw new ServiceException("箱子数量不足！");
        }
        // 扣除箱子数量
        boolean update = update(new LambdaUpdateWrapper<UserBox>()
                .setSql("quantity=quantity-" + num)
                .eq(UserBox::getId, userBox.getId())
                .ge(UserBox::getQuantity, num));
        if (!update) {
            throw new ServiceException("操作失败！");
        }

        // 销毁箱子藏品信息

    }

    /**
     * 锁定/解锁
     * @param id
     */
    @Transactional
    public void updateLock(Long id, int isLock) {
        if (isLock == 1) {
            List<UserBoxNft> list = userBoxNftService.lambdaQuery()
                    .eq(UserBoxNft::getUserBoxId, id)
                    .eq(UserBoxNft::getStatus, UserBoxNftStatusEnum.SALEABLE)
                    .list();
            if (!list.isEmpty()) {
                marketService.offShelf(MarketTypeEnum.BOX, id);
            }
        }
        this.lambdaUpdate()
                .set(UserBox::getIsLock, isLock)
                .eq(UserBox::getId, id)
                .update();
    }
}
